package com.tengits.mqttmonitor.rnlibs.mqttclient;

import android.content.Context;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.tengits.mqttmonitor.rnlibs.RNErrorConstants;

import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.android.service.MqttTraceHandler;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class MQTTClientLibraryModule extends ReactContextBaseJavaModule {
    private static final String TAG = MQTTClientLibraryModule.class.getSimpleName();

    private ReactContext mReactContext = null;
    private static Map<Integer, TengitsMqttClient> clientMap = new HashMap<>();
    private static int CLIENT_START_ID = randInt(1000, 9999);

    public MQTTClientLibraryModule(ReactApplicationContext reactContext) {
        super(reactContext);
        mReactContext = reactContext;
    }

    @NonNull
    @Override
    public String getName() {
        return "MQTTClientLibrary";
    }

    private void sendEvent(ReactContext reactContext,
                           String eventName,
                           @Nullable WritableMap params) {
        if(!reactContext.hasActiveReactInstance()) {
            return;
        }
        reactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
    }

    public static int randInt(int min, int max) {

        // NOTE: This will (intentionally) not run as written so that folks
        // copy-pasting have to think about how to initialize their
        // Random instance.  Initialization of the Random instance is outside
        // the main scope of the question, but some decent options are to have
        // a field that is initialized once and then re-used as needed or to
        // use ThreadLocalRandom (if using at least Java 1.7).
        Random rand = new Random();

        // nextInt is normally exclusive of the top value,
        // so add 1 to make it inclusive
        int randomNum = rand.nextInt((max - min) + 1) + min;

        return randomNum;
    }

    @ReactMethod
    public void addListener(String eventName) {
        // Set up any upstream listeners or background tasks as necessary
    }
    @ReactMethod
    public void removeListeners(Integer count) {
        // Remove upstream listeners, stop unnecessary background tasks
    }

    @ReactMethod
    public void createClient(final String label, ReadableMap options, Promise promise) {
        int clientRef = CLIENT_START_ID;
        CLIENT_START_ID += 1;
        TengitsMqttClient mqttClient = new TengitsMqttClient(label, clientRef, mReactContext);
        mqttClient.createClient(options);
        clientMap.put(clientRef, mqttClient);
        if(promise != null) {
            promise.resolve(clientRef);
        }
    }

    @ReactMethod
    public void connect(int clientRef, Promise promise) {
        TengitsMqttClient client = clientMap.get(clientRef);
        if(client == null) {
            promise.resolve(-1);
            return;
        }
        client.connect(promise);
    }

    @ReactMethod
    public void removeMqttClient(int clientRef, Promise promise) {
        TengitsMqttClient client = clientMap.remove(clientRef);
        if(client != null) {
            try {
                client.getMqttClient().disconnect(0, 0, new IMqttActionListener() {
                    @Override
                    public void onSuccess(IMqttToken asyncActionToken) {
                        if(promise != null) {
                            promise.resolve(RNErrorConstants.ERROR_MQTT_SUCCESS);
                        }
                    }

                    @Override
                    public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                        if(promise != null) {
                            promise.resolve(RNErrorConstants.ERROR_MQTT_SUCCESS);
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
                if(promise != null) {
                    promise.resolve(RNErrorConstants.ERROR_MQTT_SUCCESS);
                }
            }
        } else {
            if(promise != null) {
                promise.resolve(RNErrorConstants.ERROR_MQTT_SUCCESS);
            }
        }
    }

    @ReactMethod
    public void disconnect(int clientRef, Promise promise) {
        TengitsMqttClient client = clientMap.get(clientRef);
        if(client != null) {
            try {
                client.getMqttClient().disconnect(0, 0, new IMqttActionListener() {
                    @Override
                    public void onSuccess(IMqttToken asyncActionToken) {
                        if(promise != null) {
                            promise.resolve(RNErrorConstants.ERROR_MQTT_SUCCESS);
                        }
                    }

                    @Override
                    public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                        if(promise != null) {
                            promise.resolve(RNErrorConstants.ERROR_MQTT_SUCCESS);
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
                if(promise != null) {
                    promise.resolve(RNErrorConstants.ERROR_MQTT_SUCCESS);
                }
            }
        } else {
            if(promise != null) {
                promise.resolve(RNErrorConstants.ERROR_MQTT_SUCCESS);
            }
        }
    }

    @ReactMethod
    public void subscriberTopic(int clientRef, String topic, int qos, Promise promise) {
        TengitsMqttClient client = clientMap.get(clientRef);
        if(client != null) {
            try {
                client.getMqttClient().subscribe(topic, qos, null, new IMqttActionListener() {
                    @Override
                    public void onSuccess(IMqttToken asyncActionToken) {
                        if(promise != null) {
                            promise.resolve(RNErrorConstants.ERROR_MQTT_SUCCESS);
                        }
                    }

                    @Override
                    public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                        if(promise != null) {
                            promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_CLIENT_SUBSCRIBER_FAIL), "subscriberTopic failed");
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
                if(promise != null) {
                    promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_EXCEPTION), e.getMessage());
                }
            }
        } else {
            if(promise != null) {
                promise.reject( String.valueOf(RNErrorConstants.ERROR_MQTT_CLIENT_NO_CLIENT), "no client");
            }
        }
    }

    @ReactMethod
    public void unSubscriberTopic(int clientRef, String topic, Promise promise) {
        TengitsMqttClient client = clientMap.get(clientRef);
        if(client != null) {
            try {
                client.getMqttClient().unsubscribe(topic, null, new IMqttActionListener() {
                    @Override
                    public void onSuccess(IMqttToken asyncActionToken) {
                        if(promise != null) {
                            promise.resolve(RNErrorConstants.ERROR_MQTT_SUCCESS);
                        }
                    }

                    @Override
                    public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                        if(promise != null) {
                            promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_CLIENT_UNSCRIBER_FAIL), "unSubscriberTopic failed");
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
                if(promise != null) {
                    promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_EXCEPTION), e.getMessage());
                }
            }
        } else {
            if(promise != null) {
                promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_CLIENT_NO_CLIENT), "no client");
            }
        }
    }

    @ReactMethod
    public void subscriberTopics(int clientRef, ReadableArray topics, ReadableArray qos, Promise promise) {
        TengitsMqttClient client = clientMap.get(clientRef);
        if(client != null) {
            try {
                int[] intQos = new int[qos.size()];
                for(int i = 0; i < qos.size(); i++){
                    intQos[i] = qos.getInt(i);
                }
                String[] strTopics = new String[topics.size()];
                for(int j = 0; j < topics.size(); j++){
                    strTopics[j] = topics.getString(j);
                }
                client.getMqttClient().subscribe(strTopics, intQos, null, new IMqttActionListener() {
                    @Override
                    public void onSuccess(IMqttToken asyncActionToken) {
                        if(promise != null) {
                            promise.resolve(RNErrorConstants.ERROR_MQTT_SUCCESS);
                        }
                    }

                    @Override
                    public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                        if(promise != null) {
                            promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_CLIENT_SUBSCRIBER_FAIL), "subscribers failed");
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
                if(promise != null) {
                    promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_EXCEPTION), e.getMessage());
                }
            }
        } else {
            if(promise != null) {
                promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_CLIENT_NO_CLIENT), "no client");
            }
        }
    }

    @ReactMethod
    public void unSubscriberTopics(int clientRef, ReadableArray topics, Promise promise) {
        TengitsMqttClient client = clientMap.get(clientRef);
        if(client != null) {
            try {
                String[] strTopics = new String[topics.size()];
                for(int j = 0; j < topics.size(); j++){
                    strTopics[j] = topics.getString(j);
                }
                client.getMqttClient().unsubscribe(strTopics, null, new IMqttActionListener() {
                    @Override
                    public void onSuccess(IMqttToken asyncActionToken) {
                        if(promise != null) {
                            promise.resolve(RNErrorConstants.ERROR_MQTT_SUCCESS);
                        }
                    }

                    @Override
                    public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                        if(promise != null) {
                            promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_CLIENT_UNSCRIBER_FAIL), "unsubscriber failed");
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
                if(promise != null) {
                    promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_EXCEPTION), e.getMessage());
                }
            }
        } else {
            if(promise != null) {
                promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_CLIENT_NO_CLIENT), "no client");
            }
        }
    }

    @ReactMethod
    public void isConnected(int clientRef, Promise promise) {
        TengitsMqttClient client = clientMap.get(clientRef);
        if(client != null) {
            if(promise != null) {
                promise.resolve(client.getMqttClient().isConnected());
            }
        } else {
            if(promise != null) {
                promise.resolve(false);
            }
        }
    }

    @ReactMethod
    public void publishMessage(int clientRef, String topic, String message, int qos, boolean retained, Promise promise) {
        TengitsMqttClient client = clientMap.get(clientRef);
        if(client != null && client.getMqttClient().isConnected()) {
            try {
                client.getMqttClient().publish(topic, message.getBytes(), qos, retained, null, new IMqttActionListener() {
                    @Override
                    public void onSuccess(IMqttToken asyncActionToken) {
                        if(promise != null) {
                            promise.resolve(RNErrorConstants.ERROR_MQTT_SUCCESS);
                        }
                    }

                    @Override
                    public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                        if(promise != null) {
                            promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_CLIENT_PUBLISH_FAIL), "publish failed");
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
                if(promise != null) {
                    promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_EXCEPTION), e.getMessage());
                }
            }
        } else {
            promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_CLIENT_NO_CLIENT), client != null ? "no connected" : "no client");
        }
    }

    @ReactMethod
    public void getMqttClients(final String filter, Promise promise) {
        WritableArray wa = Arguments.createArray();
        for (TengitsMqttClient value : clientMap.values()) {
            if(TextUtils.isEmpty(filter)) {
                wa.pushInt(value.getClientRef());
            } else {
                if(filter.equals(value.getClientLabel())) {
                    wa.pushInt(value.getClientRef());
                }
            }
        }
        promise.resolve(wa);
    }
}
